#!/bin/bash
# iOS IPA包 Shell Script
# ⚠️⚠️使用方法：将脚本文件夹放置在桌面，参数配置好之后直接把脚本拖入终端 然后回车键即可执行
# ⚠️⚠️注意事项：由于此脚本不涉及证书及描述文件相关配置，所以需Xcode手动打包并导出成功后才能使用此脚本
############################ 参数配置 ###################################

# 终端输出颜色（检测是否为交互式终端，否则禁用）
if [ -t 1 ]; then
  CEND="\033[0m"
  CMSG="\033[32m"
  CWARNING="\033[33m"
  CFAILURE="\033[31m"
else
  CEND=""
  CMSG=""
  CWARNING=""
  CFAILURE=""
fi

# ⚠️默认 Release 版，也可配置为 Debug
pro_environ=Release
# 项目路径(文件绝对路径，如"/Users/xxx/BaseProject/BaseProject.xcworkspace"，如果是不含Pod的工程，绝对路径"/Users/xxx/BaseProject/BaseProject.xcodeproj")
pro_path=""
# 是否在打包前自动递增 CFBundleVersion（构建号）
auto_increment_build_number="true"

# ⚠️自动上传蒲公英(api_key)获取地址https://www.pgyer.com/doc/view/api#fastUploadApp
api_key=""        # 不上传则不填
# ukey=""         # 新版API不再需要ukey，已移除
pgy_installType=2  # 1、公开发布 2、密码安装
pgy_password=""   # 如果设置了密码安装则需要密码
upload_content="" # 更新内容

# ⚠️自动上传 App Store - 账号密码方式（建议仅在无法使用 API Key 时采用）
# apple_pwd 必须填写 Apple ID 的「App 专用密码」，流程如下：
#   1. 打开 https://appleid.apple.com ，使用上传 ipa 的 Apple ID 登录。
#   2. 在“登录与安全”模块找到「App 专用密码」，点击“生成密码…”。
#   3. 输入一个备注（任意英文标签），确认后会得到形如 xxxx-xxxx-xxxx-xxxx 的 16 位专用密码。
#   4. 复制该密码并填入 apple_pwd（或设置环境变量 ASC_APP_PASSWORD），不要使用 Apple ID 登录密码。
#   5. 首次使用专用密码前，请确保该 Apple ID 已开启双重认证，否则无法生成。
apple_id=""     # 不上传则不填，推荐使用环境变量 ASC_APPLE_ID
apple_pwd=""    # App 专用密码；推荐使用环境变量 ASC_APP_PASSWORD 注入
# asc_provider 是团队短名称（Team Short Name 或 Provider Short Name），
# 可在 Transporter / App Store Connect -> Users and Access -> App Store Connect API -> Keys 的“View Provider Short Name”中查看。
asc_provider="" # 例如：ABCDE12345，可选，可通过环境变量 ASC_PROVIDER 指定（与 asc_api_key_id 不同）

# App Store Connect API Key（推荐）获取流程：
#   1. 登录 App Store Connect -> Users and Access -> Keys（App Store Connect API）。
#   2. 点击“+”创建 API Key，记录生成的 Key ID（10 位字符）和 Issuer ID（UUID）。
#   3. 下载得到的 AuthKey_<KEYID>.p8 文件并妥善保存。
# 配置说明：
#   - asc_api_key_id：Key ID（仅 10 字符），与 asc_provider 无关。
#   - asc_api_key_issuer：Issuer ID（UUID），与 apple_pwd 完全不同。
#   - asc_api_key_file：AuthKey_<KEYID>.p8 的绝对路径或相对于脚本目录的路径。
asc_api_key_id=""     # 例如：ABC123DEF4，可通过环境变量 ASC_API_KEY_ID 指定
asc_api_key_issuer="" # 例如：11223344-5566-7788-9900-AABBCCDDEEFF，可通过环境变量 ASC_API_ISSUER 指定
asc_api_key_file=""   # AuthKey_<KEYID>.p8 文件路径，可通过环境变量 ASC_API_KEY_FILE 指定

# fir.im (Betaqr) 托管平台上传配置，详见 https://www.betaqr.com.cn/docs
fir_api_token=""      # 必填，用于鉴权，可通过环境变量 FIR_API_TOKEN 指定
fir_app_id=""         # 可选，应用 id，和 short 二选一
fir_app_short=""      # 可选，应用 short 链接，和 id 二选一
fir_release_type="adhoc" # 可选，adhoc / inhouse / development，默认 adhoc
fir_changelog=""      # 可选，更新说明，默认继承 upload_content
############################ 参数配置 ###################################

# 支持通过环境变量注入敏感信息，避免将账号硬编码在脚本中
if [ -z "${api_key}" ] && [ -n "${PGYER_API_KEY}" ]; then
  api_key="${PGYER_API_KEY}"
fi
if [ -z "${pgy_password}" ] && [ -n "${PGYER_PASSWORD}" ]; then
  pgy_password="${PGYER_PASSWORD}"
fi
if [ -z "${upload_content}" ] && [ -n "${PGYER_UPDATE_DESCRIPTION}" ]; then
  upload_content="${PGYER_UPDATE_DESCRIPTION}"
fi

if [ -z "${apple_id}" ] && [ -n "${ASC_APPLE_ID}" ]; then
  apple_id="${ASC_APPLE_ID}"
fi
if [ -z "${apple_pwd}" ] && [ -n "${ASC_APP_PASSWORD}" ]; then
  apple_pwd="${ASC_APP_PASSWORD}"
fi
if [ -z "${asc_provider}" ] && [ -n "${ASC_PROVIDER}" ]; then
  asc_provider="${ASC_PROVIDER}"
fi

if [ -z "${asc_api_key_id}" ] && [ -n "${ASC_API_KEY_ID}" ]; then
  asc_api_key_id="${ASC_API_KEY_ID}"
fi
if [ -z "${asc_api_key_issuer}" ] && [ -n "${ASC_API_ISSUER}" ]; then
  asc_api_key_issuer="${ASC_API_ISSUER}"
fi
if [ -z "${asc_api_key_file}" ] && [ -n "${ASC_API_KEY_FILE}" ]; then
  asc_api_key_file="${ASC_API_KEY_FILE}"
fi
if [[ -n "${asc_api_key_file}" && "${asc_api_key_file}" == ~* ]]; then
  asc_api_key_file="${asc_api_key_file/#\~/${HOME}}"
fi

if [ -z "${fir_api_token}" ] && [ -n "${FIR_API_TOKEN}" ]; then
  fir_api_token="${FIR_API_TOKEN}"
fi
if [ -z "${fir_app_id}" ] && [ -n "${FIR_APP_ID}" ]; then
  fir_app_id="${FIR_APP_ID}"
fi
if [ -z "${fir_app_short}" ] && [ -n "${FIR_APP_SHORT}" ]; then
  fir_app_short="${FIR_APP_SHORT}"
fi
if [ -z "${fir_release_type}" ] && [ -n "${FIR_RELEASE_TYPE}" ]; then
  fir_release_type="${FIR_RELEASE_TYPE}"
fi
if [ -z "${fir_changelog}" ] && [ -n "${FIR_CHANGELOG}" ]; then
  fir_changelog="${FIR_CHANGELOG}"
fi

printf "
#######################################################################
#                     自动打包脚本
#######################################################################
"

# 截取项目全称 （如：BaseProject.xcworkspace --> 项目名称、项目后缀）
pro_full_name=${pro_path##*/}
# 分割得 项目名称、项目后缀
pro_array=(${pro_full_name//./ })
pro_name=${pro_array[0]}
pro_suffix=${pro_array[1]}
# 项目文件夹路径
pro_path=${pro_path%/*}

# 判断项目全称是否配置正确
if [ "${pro_suffix}" != "xcworkspace" ] && [ "${pro_suffix}" != "xcodeproj" ]; then
  echo "${CWARNING}⚠️项目名称配置错误，请正确配置project_full_name，如：BaseProject.xcworkspace 或 BaseProject.xcodeproj类型${CEND}"
  exit
fi

# 打包导出类型(根据 plist 文件决定)
plist_name=""

# 上传目标平台（支持多选）
declare -a upload_targets

while :; do
  printf "\n选择上传平台（可输入多个编号，使用英文逗号分隔）：\n"
  printf "   ${CMSG}0${CEND}.不上传\n"
  printf "   ${CMSG}1${CEND}.蒲公英\n"
  printf "   ${CMSG}2${CEND}.App Store\n"
  printf "   ${CMSG}3${CEND}.fir.im (Betaqr)\n"
  printf "   ${CMSG}q${CEND}.退出打包脚本\n\n"
  read -p "请输入上传平台编号: " platform_number
  if [[ ${platform_number} == "q" ]]; then
    exit
  fi

  platform_number=${platform_number// /}
  IFS=',' read -ra selections <<< "${platform_number}"
  upload_targets=()
  valid_selection=true

  for selection in "${selections[@]}"; do
    if [ -z "${selection}" ]; then
      valid_selection=false
      break
    fi
    case "${selection}" in
      0)
        upload_targets=()
        ;;
      1)
        upload_targets+=("pgyer")
        ;;
      2)
        upload_targets+=("appstore")
        ;;
      3)
        upload_targets+=("fir")
        ;;
      *)
        valid_selection=false
        ;;
    esac
  done

  if ! ${valid_selection}; then
    echo "${CFAILURE}⚠️输入错误! 只允许输入 0 ~ 3，多个请使用英文逗号分隔${CEND}"
    continue
  fi

  if [ ${#upload_targets[@]} -eq 0 ] && [ "${selections[0]}" == "0" ]; then
    echo "============ 未选择上传平台，将仅执行打包流程 ======="
    break
  fi

  if [ ${#upload_targets[@]} -gt 1 ]; then
    tmp_targets=()
    for target in "${upload_targets[@]}"; do
      skip=false
      for existing in "${tmp_targets[@]}"; do
        if [ "${existing}" == "${target}" ]; then
          skip=true
          break
        fi
      done
      if ! ${skip}; then
        tmp_targets+=("${target}")
      fi
    done
    upload_targets=("${tmp_targets[@]}")
  fi

  echo "============ 已选择上传平台: ${upload_targets[*]} ======="
  break
done

while :; do
  printf "
选择你的打包版本类型：
   ${CMSG}1${CEND}.Developers(开发版)
   ${CMSG}2${CEND}.App Store(正式版)
   ${CMSG}3${CEND}.AdHoc(测试版)
   ${CMSG}4${CEND}.Enterprise(企业版)
   ${CMSG}q${CEND}.退出打包脚本\n
"
  read -p "请输入打包类型: " number
  if [[ ! ${number} =~ ^[1-4,q]$ ]]; then
    echo "${CFAILURE}⚠️输入错误! 只允许输入 1 ~ 4 和 q${CEND}"
  else
    case "${number}" in
      1)
        plist_name="DevelopmentExportOptions.plist"
        break
        ;;
      2)
        plist_name="AppStoreExportOptions.plist"
        break
        ;;
      3)
        plist_name="AdHocExportOptions.plist"
        break
        ;;
      4)
        plist_name="EnterpriseExportOptions.plist"
        break
        ;;
      q)
        exit
        ;;
    esac
  fi
done

# 根据需求判断上一步是否执行成功，传入执行结果：$? "执行步骤名"
judgementLastIsSuccsess() {
  if [ "$1" -eq 0 ]; then
    echo -e "\n⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️ $2 操 作 成 功 ! ⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️\n"
  else
    echo -e "\n😭😭😭😭😭😭😭😭 $2操作失败，终止脚本 ! 😭😭😭😭😭😭😭😭\n"
    exit
  fi
}

# 时间转换函数（秒转分钟）
timeTransformation() {
  if [ "$1" -le 0 ]; then
    echo "============ ⚠️请检查项目是否能正常手动打包并导出ipa文件 ======="
    exit
  fi

  if [ "$1" -gt 59 ]; then
    local t_min=$(( $1 / 60 ))
    local t_second=$(( $1 % 60 ))
    echo "============ 本次$2用时：${t_min}分${t_second}秒 ======="
  else
    echo "============ 本次$2用时：$1秒 ======="
  fi
}

autoIncrementBuildNumber() {
  echo "============ 自动检查并递增构建号 ======="

  local build_settings_output
  if ! build_settings_output=$(xcodebuild -showBuildSettings -"${pro_clean}" "${pro_full_name}" -scheme "${pro_name}" -configuration "${pro_environ}" 2>/dev/null); then
    echo "${CWARNING}⚠️无法获取构建设置，跳过自动递增构建号${CEND}"
    return
  fi

  local info_plist_path_raw
  info_plist_path_raw=$(printf "%s\n" "${build_settings_output}" | awk -v target="${pro_name}" '
    /^Build settings for action/ { capture=0 }
    /^Build settings for action[^"]* target / {
      capture=0
      if ($0 ~ "target " target "(:|$)") {
        capture=1
      }
      next
    }
    capture && /^[[:space:]]*INFOPLIST_FILE[[:space:]]*=/ {
      sub(/^[^=]*=[[:space:]]*/, "")
      gsub(/[[:space:]]+$/, "")
      gsub(/^[[:space:]]+/, "")
      print
      exit
    }
  ')
  info_plist_path_raw=${info_plist_path_raw%$'\r'}

  if [ -z "${info_plist_path_raw}" ]; then
    info_plist_path_raw=$(printf "%s\n" "${build_settings_output}" | sed -n 's/^[[:space:]]*INFOPLIST_FILE[[:space:]]*=[[:space:]]*//p' | head -n 1)
    info_plist_path_raw=${info_plist_path_raw%$'\r'}
  fi

  if [ -z "${info_plist_path_raw}" ]; then
    echo "${CWARNING}⚠️未能解析 Info.plist 路径，跳过自动递增构建号${CEND}"
    return
  fi

  local srcroot project_dir source_root
  srcroot=$(printf "%s\n" "${build_settings_output}" | sed -n 's/^[[:space:]]*SRCROOT[[:space:]]*=[[:space:]]*//p' | head -n 1)
  srcroot=${srcroot%$'\r'}
  project_dir=$(printf "%s\n" "${build_settings_output}" | sed -n 's/^[[:space:]]*PROJECT_DIR[[:space:]]*=[[:space:]]*//p' | head -n 1)
  project_dir=${project_dir%$'\r'}
  source_root=$(printf "%s\n" "${build_settings_output}" | sed -n 's/^[[:space:]]*SOURCE_ROOT[[:space:]]*=[[:space:]]*//p' | head -n 1)
  source_root=${source_root%$'\r'}

  if [ -z "${srcroot}" ]; then
    srcroot="${pro_path}"
  fi
  if [ -z "${project_dir}" ]; then
    project_dir="${pro_path}"
  fi
  if [ -z "${source_root}" ]; then
    source_root="${srcroot}"
  fi

  local info_plist_path="${info_plist_path_raw}"
  info_plist_path=${info_plist_path//\$\(SRCROOT\)/${srcroot}}
  info_plist_path=${info_plist_path//\$\(PROJECT_DIR\)/${project_dir}}
  info_plist_path=${info_plist_path//\$\(SOURCE_ROOT\)/${source_root}}
  info_plist_path=$(printf "%s" "${info_plist_path}" | sed 's/[[:space:]]*$//')

  if [[ "${info_plist_path}" != /* ]]; then
    info_plist_path="${pro_path}/${info_plist_path}"
  fi

  if [ ! -f "${info_plist_path}" ]; then
    echo "${CWARNING}⚠️Info.plist 文件不存在：${info_plist_path}，跳过自动递增构建号${CEND}"
    return
  fi

  echo "============ 目标 Info.plist 路径：${info_plist_path} ======="

  local current_build
  current_build=$(/usr/libexec/PlistBuddy -c "Print :CFBundleVersion" "${info_plist_path}" 2>/dev/null)

  if [ -z "${current_build}" ]; then
    echo "${CWARNING}⚠️无法读取 CFBundleVersion，跳过自动递增构建号${CEND}"
    return
  fi

  local next_build=""
  if [[ "${current_build}" =~ ^[0-9]+$ ]]; then
    next_build=$(( current_build + 1 ))
  elif [[ "${current_build}" =~ ^[0-9]+(\.[0-9]+)+$ ]]; then
    local IFS_backup="${IFS}"
    local version_parts=()
    IFS='.' read -r -a version_parts <<< "${current_build}"
    IFS="${IFS_backup}"
    local last_index=$(( ${#version_parts[@]} - 1 ))
    if [[ "${version_parts[$last_index]}" =~ ^[0-9]+$ ]]; then
      version_parts[$last_index]=$(( version_parts[$last_index] + 1 ))
      local joined=""
      local idx
      for idx in "${!version_parts[@]}"; do
        if [ -n "${joined}" ]; then
          joined="${joined}.${version_parts[$idx]}"
        else
          joined="${version_parts[$idx]}"
        fi
      done
      next_build="${joined}"
    fi
  fi

  if [ -z "${next_build}" ]; then
    echo "${CWARNING}⚠️当前构建号格式（${current_build}）无法自动递增，请手动处理${CEND}"
    return
  fi

  if /usr/libexec/PlistBuddy -c "Set :CFBundleVersion ${next_build}" "${info_plist_path}" >/dev/null 2>&1; then
    echo "============ CFBundleVersion: ${current_build} -> ${next_build} ======="
  else
    echo "${CWARNING}⚠️更新 CFBundleVersion 失败，保持原始值 ${current_build}${CEND}"
  fi
}

# 打包开始时间（用于计算打包脚本执行时间）
begin_time=$(date +%s)
# 获取系统时间
date_string=$(date +"%Y-%m-%d~%H.%M.%S")

# 获取脚本当前所在目录(即上级目录绝对路径)
root_dir=$(cd "$(dirname "$0")" && pwd)
# IPA 文件导出时使用的 plist 文件路径
plist_path="${root_dir}/LSExportOptions/${plist_name}"

# 切换到当前脚本的工作目录
cd "${root_dir}" || exit

# 所有打包文件导出时的临时存放目录（IPA、Achieve）
temp_path="${root_dir}/ExportIPAFile"
if [ ! -d "${temp_path}" ]; then
  mkdir -p "${temp_path}"
fi

# 切换到 temp_path 目录去创建存放 Archive 和 IPA 的文件夹
cd "${temp_path}" || exit
ipa_dir="${pro_name}${date_string}"
mkdir "${ipa_dir}"

# 切换到项目根目录开始打包操作
cd "${pro_path}" || exit

echo "============ ${pro_name} 打包开始 ======="

# 如果没有使用cocoapods 反之if会处理
pro_clean=project
if [ "${pro_suffix}" == "xcworkspace" ]; then
  pro_clean=workspace
fi

if [ "${auto_increment_build_number}" = "true" ]; then
  autoIncrementBuildNumber
fi

# 先组装 archive_path、ipa_path，用于导出 ipa 和 上传
archive_path="${temp_path}/${ipa_dir}/${pro_name}.xcarchive"
ipa_path="${temp_path}/${ipa_dir}/${pro_name}.ipa"

# Clean操作
xcodebuild clean -${pro_clean} "${pro_full_name}" -scheme "${pro_name}" -configuration "${pro_environ}"
judgementLastIsSuccsess $? "Clean"

# Archive操作
xcodebuild archive -${pro_clean} "${pro_full_name}" -scheme "${pro_name}" -archivePath "${archive_path}" -destination 'generic/platform=iOS' -allowProvisioningUpdates
judgementLastIsSuccsess $? "Archive"

# 导出IPA文件操作
xcodebuild -exportArchive -archivePath "${archive_path}" -exportOptionsPlist "${plist_path}" -exportPath "${temp_path}/${ipa_dir}" -allowProvisioningUpdates
judgementLastIsSuccsess $? "导出IPA文件"

if [ ! -f "${ipa_path}" ]; then
  alt_ipa=$(find "${temp_path}/${ipa_dir}" -maxdepth 1 -type f -name "*.ipa" | head -n 1)
  if [ -n "${alt_ipa}" ]; then
    echo "${CWARNING}⚠️检测到导出 IPA 文件名为: ${alt_ipa##*/}，请确认是否与 Xcode 中的 PRODUCT_NAME 设置一致${CEND}"
    ipa_path="${alt_ipa}"
  else
    echo "${CFAILURE}⚠️未在导出目录找到 IPA 文件，请检查导出配置${CEND}"
    exit
  fi
fi

# 删除 xcarchive 包
rm -r "${archive_path}"

# 打包结束时间
end_time=$(date +%s)
# 计算打包时间(秒：s)
cost_time=$(( end_time - begin_time ))
# 调用时间转换函数
timeTransformation "${cost_time}" "打包"

echo "============ ${pro_name} 自动打包完成 ======="

# 打开 当前的 ipa 存放文件夹
open "${temp_path}/${ipa_dir}"

########################## 上传蒲公英 #################################
uploadPGY() {
  # 判断api_key配置是否为空，空则代表不上传
  if [ -z "${api_key}" ]; then
    echo "============ 请先配置蒲公英的 api_key ======="
    return
  fi

  # 上传开始时间
  upload_start_time=$(date +%s)

  # 开始上传
  echo "============ 正在上传 ${pro_name} 到 蒲公英 ======="

  # 使用新版API地址和参数名称
  curl -F "file=@${ipa_path}" \
    -F "_api_key=${api_key}" \
    -F "buildInstallType=${pgy_installType}" \
    -F "buildPassword=${pgy_password}" \
    -F "buildUpdateDescription=${upload_content}" \
    https://www.pgyer.com/apiv2/app/upload
  judgementLastIsSuccsess $? "上传蒲公英"
  echo "============ 上传结束 ======="

  # 上传结束时间
  upload_end_time=$(date +%s)

  # 计算上传时间(秒：s)
  upload_time=$(( upload_end_time - upload_start_time ))

  # 调用时间转换函数
  timeTransformation "${upload_time}" "上传蒲公英"
}

########################## 上传 fir.im (Betaqr) #################################
uploadFIR() {
  if [ -z "${fir_api_token}" ]; then
    echo "============ 请先配置 fir.im 的 api_token ======="
    return
  fi

  if [ -z "${fir_app_id}" ] && [ -z "${fir_app_short}" ]; then
    echo "============ 请配置 fir.im 的应用 id 或 short ======="
    return
  fi

  local release_type="${fir_release_type}"
  if [ -z "${release_type}" ]; then
    release_type="adhoc"
  fi

  local changelog_to_send="${fir_changelog}"
  if [ -z "${changelog_to_send}" ]; then
    changelog_to_send="${upload_content}"
  fi

  local upload_start_time upload_end_time upload_time upload_exit_code
  upload_start_time=$(date +%s)
  echo "============ 正在上传 ${pro_name} 到 fir.im ======="

  local curl_cmd=(curl -F "file=@${ipa_path}" -F "api_token=${fir_api_token}" -F "release_type=${release_type}")

  if [ -n "${fir_app_id}" ]; then
    curl_cmd+=(-F "id=${fir_app_id}")
  else
    curl_cmd+=(-F "short=${fir_app_short}")
  fi

  if [ -n "${changelog_to_send}" ]; then
    curl_cmd+=(-F "changelog=${changelog_to_send}")
  fi

  curl_cmd+=(-F "build_name=${pro_name}")
  curl_cmd+=("https://www.betaqr.com/api/v3/apps/upload")

  "${curl_cmd[@]}"
  upload_exit_code=$?

  judgementLastIsSuccsess "${upload_exit_code}" "上传 fir.im"
  echo "============ 上传结束 ======="

  upload_end_time=$(date +%s)
  upload_time=$(( upload_end_time - upload_start_time ))
  timeTransformation "${upload_time}" "上传 fir.im"
}

########################## 上传苹果商店 #################################
uploadAppStore() {
  local use_api_key="false"
  local key_id_upper=""
  local keys_dir="${HOME}/.appstoreconnect/private_keys"
  local expected_key_file=""
  local cleanup_key_file=""
  local resolved_api_key_file="${asc_api_key_file}"

  if [ -n "${resolved_api_key_file}" ] && [ ! -f "${resolved_api_key_file}" ] && [ -n "${root_dir}" ] && [ -f "${root_dir}/${resolved_api_key_file}" ]; then
    resolved_api_key_file="${root_dir}/${resolved_api_key_file}"
  fi

  if [ -n "${asc_api_key_id}" ] && [ -n "${asc_api_key_issuer}" ]; then
    use_api_key="true"
    key_id_upper=$(echo "${asc_api_key_id}" | tr '[:lower:]' '[:upper:]')
    expected_key_file="${keys_dir}/AuthKey_${key_id_upper}.p8"

    if [ -n "${resolved_api_key_file}" ]; then
      if [ ! -f "${resolved_api_key_file}" ]; then
        echo "${CFAILURE}⚠️指定的 App Store Connect API Key 文件不存在: ${resolved_api_key_file}${CEND}"
        return
      fi
      mkdir -p "${keys_dir}"
      if [ ! -f "${expected_key_file}" ]; then
        cp "${resolved_api_key_file}" "${expected_key_file}"
        chmod 600 "${expected_key_file}"
        cleanup_key_file="${expected_key_file}"
      fi
    fi

    if [ ! -f "${expected_key_file}" ]; then
      echo "${CWARNING}⚠️未在默认目录找到 AuthKey_${key_id_upper}.p8，若上传失败请确认密钥文件已放置在 ~/.appstoreconnect/private_keys/${CEND}"
    fi
  fi

  if [ "${use_api_key}" != "true" ]; then
    if [ -z "${apple_id}" ] || [ -z "${apple_pwd}" ]; then
      echo "${CWARNING}⚠️未配置 App Store Connect API Key 或 Apple ID 凭据，跳过上传 App Store${CEND}"
      return
    fi
    if [[ ! "${apple_pwd}" =~ ^[A-Za-z0-9]{4}-[A-Za-z0-9]{4}-[A-Za-z0-9]{4}-[A-Za-z0-9]{4}$ ]]; then
      echo "${CWARNING}⚠️提示：apple_pwd 应填写 Apple ID 的 App 专用密码（格式 xxxx-xxxx-xxxx-xxxx），请前往 appleid.apple.com 生成并替换${CEND}"
    fi
  fi

  local upload_cmd=(xcrun altool --upload-app -f "${ipa_path}" -t ios --output-format xml)
  if [ "${use_api_key}" = "true" ]; then
    upload_cmd+=(--apiKey "${key_id_upper}" --apiIssuer "${asc_api_key_issuer}")
  else
    upload_cmd+=(-u "${apple_id}" -p "${apple_pwd}")
  fi
  if [ -n "${asc_provider}" ]; then
    upload_cmd+=(--asc-provider "${asc_provider}")
  fi

  local upload_start_time upload_end_time upload_time upload_exit_code
  upload_start_time=$(date +%s)
  echo "============ App Store 上传开始 ======="
  local upload_output
  upload_output=$("${upload_cmd[@]}" 2>&1)
  upload_exit_code=$?
  printf "%s\n" "${upload_output}"

  if [ "${upload_exit_code}" -eq 0 ]; then
    if echo "${upload_output}" | grep -E "ERROR:|ENTITY_ERROR|Failed to upload package" >/dev/null 2>&1; then
      upload_exit_code=1
    fi
  fi

  if [ -n "${cleanup_key_file}" ] && [ -f "${cleanup_key_file}" ]; then
    rm -f "${cleanup_key_file}"
  fi

  judgementLastIsSuccsess "${upload_exit_code}" "上传App Store"
  echo "============ App Store 上传结束 ======="

  upload_end_time=$(date +%s)
  upload_time=$(( upload_end_time - upload_start_time ))
  timeTransformation "${upload_time}" "上传App Store"
}

if [ ${#upload_targets[@]} -eq 0 ]; then
  echo "============ 未选择任何上传平台，流程结束 ======="
else
  for target in "${upload_targets[@]}"; do
    case "${target}" in
      pgyer)
        uploadPGY
        ;;
      appstore)
        if [ "${plist_name}"x != "AppStoreExportOptions.plist"x ]; then
          echo "${CWARNING}⚠️提示：当前导出选项非 App Store，仍尝试上传 App Store，请确保 ipa 符合要求${CEND}"
        fi
        uploadAppStore
        ;;
      fir)
        uploadFIR
        ;;
      *)
        echo "${CFAILURE}⚠️未识别的上传平台: ${target}${CEND}"
        ;;
    esac
  done
fi

